import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.util.Collection;
import java.util.TreeMap;

/**
 * Authors - Oren Bracha, Rony Lahav and Yehuda Eterman
 *
 * the updater starts the handling of the update cycle
 * does 3 tries of UDP and 1 of TCP
 */
public class Updater implements Runnable{
	public void run() {
		do {
			// Get from the DB all of the tuples to which I should send updates to
			TreeMap<String,DBTuple> peersToUpdate = (TreeMap<String,DBTuple>)DataBase.getInstance().getAllTuplesForUpdate();

			//run over all peers and send an update message according to the protocol
			Collection<DBTuple> c = peersToUpdate.values();
			for (DBTuple tuple : c) {
				if (proj350.isShutDown()) {
					break;

				}

				// Open a dedicated thread to send update to the specific peer
				SingleUpdate singleUpdate = new SingleUpdate(tuple, proj350.getNewUniqueID());
				Thread thread = new Thread(singleUpdate);
				proj350.addThreadToLivingList(singleUpdate.getUniqueID(), thread);
				thread.start();

				try{
					// Check if meanwhile I have to sleep
					if (proj350.getSleepPeriod() > 0){
						Thread.sleep(proj350.getSleepPeriod()*1000);
					}
				}
				//catch interrupt and continue with the loop
				catch (InterruptedException e){
				}
			}
			
			try{
				// Sleep until next update frequency
				Thread.sleep(proj350.getUpdateFrequency()*1000);
			}
			catch (InterruptedException e){

			}
		} while (!proj350.isShutDown());
	}

	/**
	 * trying 3 times to send an update to another peer 
	 * @param tuple
	 * @return true if got a response in 1 of 3 attempts to send
	 */
	public boolean UDPAttempt(DBTuple tuple) {
		boolean UDPConnSucc = false;
		int counter = 0;
		
		while (counter < proj350.UDP_ATTEMPTS_NUMBER && !UDPConnSucc & proj350.getSleepPeriod() == 0){
			 DatagramSocket ds = null;
			try {	
				ds = new DatagramSocket();
			
				String message = createUpdateMessage();

				//create udp packet for an update message and send it to the designated peer
				DatagramPacket dp = new DatagramPacket(message.getBytes(), message.length(), InetAddress.getByName(tuple.getIp()),Integer.parseInt(tuple.getUdpPort()));	
				ds.send(dp);
				Logger.getInstance().printInOutMsg(false, message, proj350.getMyPeerName(), proj350.getMyIp(), Integer.toString(ds.getLocalPort()), tuple.getPeerName(), tuple.getIp(), tuple.getUdpPort(), "udp");
				++counter;

				ds.setSoTimeout(proj350.TIMEOUT_IDLE_PERIOD);

				//receiving response for the update
				byte[] buffer = new byte[1024];
				DatagramPacket rcvPacket = new DatagramPacket(buffer, buffer.length);
				ds.receive(rcvPacket);
				UDPConnSucc = true;

				//create a UDPSenderConnection to handle the transaction of the requests for new data
				UDPSenderConnection udpConnSender = new UDPSenderConnection(tuple.getPeerName(),tuple.getIp(),ds,rcvPacket.getPort(),rcvPacket,proj350.getNewUniqueID());
				Thread thread  = new Thread(udpConnSender);
				proj350.addThreadToLivingList(udpConnSender.getUniqueID(), thread);
				thread.start();
				}
			catch (SocketTimeoutException e){
				if (ds != null && !ds.isClosed()){
					ds.close();
				}
			}
			catch (IOException e){
			}
		}
		return UDPConnSucc;
	}

	/**
	 * trying to send an update by TCP
	 * @param tuple
	 * @return true if connection established correctly
	 */
	public boolean TCPAttempt(DBTuple tuple) {
		try{
			//create socket for TCP data transfer
			Socket socket = new Socket(tuple.getIp(),Integer.parseInt(tuple.getTcpPort()));
			
			//create a TCPSenderConnection to run a transaction of updates 
			TCPSenderConnection tcpSenderConn = new TCPSenderConnection(socket, tuple.getPeerName(), tuple.getIp(),proj350.getNewUniqueID());
			Thread thread = new Thread(tcpSenderConn);
			proj350.addThreadToLivingList(tcpSenderConn.getUniqueID(), thread);
			thread.start();
			return true;
		}
		catch (IOException e){
			return false;
		}
	}

	/**
	 * create an update message according to the correct format
	 * @return the update message
	 */
	public String createUpdateMessage() {
		DBTuple tuple = DataBase.getInstance().findTuple(proj350.getMyPeerName());
		String message = MessageCenter.getInstance().createMessage_Update(tuple.getDbVersion(), tuple.getPeerName());
		return message;
	}
}
